
import os
import sys
import functools

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.Qsci import *

sys.path.append(os.path.dirname(__file__))
from ctags_parser import Ctags_parser
from lexer import MyLexer

node_editors = {}

# 光标历史位置
cursor_list = []
cur_cursor = 0


context = None
def init(context_):
    global context
    context = context_


# 光标处理函数
def goback_cursor():
    global cursor_list
    global cur_cursor

    if cur_cursor <= 0:
        return None
    
    cur_cursor -= 1
    return cursor_list[cur_cursor-1]

def goforward_cursor():
    global cursor_list
    global cur_cursor

    if cur_cursor >= len(cursor_list):
        return None
    else:
        cur_cursor += 1
        return cursor_list[cur_cursor-1]

def cursor_change(file_path, line, index):
    global cursor_list
    global cur_cursor

    if len(cursor_list) == 0:
        cursor_list.append({'url':file_path, 'line':line, 'index': index})
        cur_cursor += 1
    else:
        if cursor_list[cur_cursor-1]['url'] != file_path:
            cursor_list.append({})
            cur_cursor += 1
            if len(cursor_list) > cur_cursor:
                cursor_list = cursor_list[0:cur_cursor]
        elif cursor_list[cur_cursor-1]['line'] - line > 10 or  cursor_list[cur_cursor-1]['line'] - line < -10:
            cursor_list.append({})
            cur_cursor += 1
            if len(cursor_list) > cur_cursor:
                cursor_list = cursor_list[0:cur_cursor]
        
        cursor_list[cur_cursor-1] = {'url':file_path, 'line':line, 'index': index}
        

ex_config = {
    "avs": QsciLexerAVS(),
    "sh": QsciLexerBash(),
    "cmakelists.txt": QsciLexerCMake(),
    "c": QsciLexerCPP(),
    "cpp": QsciLexerCPP(),
    "h": QsciLexerCPP(),
    "cs": QsciLexerCSharp(),
    "java": QsciLexerJava(),
    "js": QsciLexerJavaScript(),
    "css": QsciLexerCSS(),
    "html": QsciLexerHTML(),
    "xml": QsciLexerXML(),
    "json": QsciLexerJSON(),
    "lua": QsciLexerLua(),
    "py": QsciLexerPython(),
    "md": QsciLexerMarkdown()

}



'''================================================================================'''
'''|                                  EDITOR                                      |'''
'''================================================================================'''
class ScintillaEditor(QsciScintilla):
    
    def __init__(self, parent=None):
        super(ScintillaEditor, self).__init__()
        self.__show_symbol_handle = None

        self._parent = parent
        self._url = None
        self._init = False
        
        self._find = {}

        self.selAllShort = QShortcut(QKeySequence("Ctrl+s"), self)
        self.selAllShort.activated.connect(self.on_save)
        
        self.findShort = QShortcut(QKeySequence("Ctrl+f"), self)
        self.findShort.activated.connect(self.on_find)
        
        self.findNextShort = QShortcut(QKeySequence("F3"), self)
        self.findNextShort.activated.connect(self.on_find_next)
        
        self.findPreShort = QShortcut(QKeySequence("Shift+F3"), self)
        self.findPreShort.activated.connect(self.on_find_pre)

        self.goDefine = QShortcut(QKeySequence("F12"), self)
        self.goDefine.activated.connect(self.on_go_define)

        self.textChanged.connect(self.onTextChanged)

        self.setAutoIndent(True)
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)

        self.setEolMode(QsciScintilla.EolUnix)

        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
        self.setAutoCompletionSource(self.AcsAll)  # 自动补全。对于所有Ascii字符
        self.setAutoCompletionCaseSensitivity(False)  # 自动补全大小写敏感
        self.setAutoCompletionThreshold(1)  # 输入多少个字符才弹出补全提示

        # -------------------------------- #
        #     QScintilla editor setup      #
        # -------------------------------- #

        #self.setText(text)
        self.setLexer(None)  # We install lexer later
        self.setUtf8(True)  # Set encoding to UTF-8

        # 1. Text wrapping
        # -----------------
        self.setWrapMode(QsciScintilla.WrapWord)
        self.setWrapVisualFlags(QsciScintilla.WrapFlagByText)
        self.setWrapIndentMode(QsciScintilla.WrapIndentIndented)

        # 2. End-of-line mode
        # --------------------
        self.setEolMode(QsciScintilla.EolWindows)
        self.setEolVisibility(False)

        # 3. Indentation
        # ---------------
        self.setIndentationsUseTabs(False)
        self.setTabWidth(4)
        self.setIndentationGuides(True)
        self.setTabIndents(True)
        self.setAutoIndent(True)

        # 4. Caret
        # ---------
        self.setCaretForegroundColor(QColor("#ff0000ff"))
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#1f0000ff"))
        self.setCaretWidth(2)

        # 5. Margins
        # -----------
        self.setMarginType(0, QsciScintilla.NumberMargin)
        self.setMarginWidth(0, "00000000")
        self.setMarginsForegroundColor(QColor("#ff888888"))

        # -------------------------------- #
        #          Install lexer           #
        # -------------------------------- #
        self.__lexer = MyLexer(self)
        self.setLexer(self.__lexer)

        # -------------------------------- #
        #  Connect function to indicators  #
        # -------------------------------- #
        #self.indicatorClicked.connect(self.indicator_clicked)

    ''''''

    def indicator_clicked(self, line, index, keys):
        #self.indicator_clicked_delayed( line, index, keys)
        QTimer.singleShot(100, functools.partial(
            self.indicator_clicked_delayed, line, index, keys))
        
    ''''''
    def on_go_define(self):
        line, col = self.getCursorPosition()
        self.indicator_clicked_delayed(line, col, None)

    def indicator_clicked_delayed(self, line, index, keys):
        global context

        pos = self.positionFromLineIndex(line, index)
        print("pos:", line, index, pos)

        start = self.SendScintilla(QsciScintilla.SCI_INDICATORSTART, 0, pos)
        end = self.SendScintilla(QsciScintilla.SCI_INDICATOREND, 0, pos)
        #text = self.text()[start:end]
        text = self.text(start, end)
        #print('text:', text, text2)
        print("pos:", line, index, pos, start, end)
        print("indicator '{}' clicked in line '{}', index '{}'".format(text, line, index))

        relPath, line = self.__lexer.parser.where_to_jump(text)
        linefocus = int(line) - 1
        print("jump to file: " + relPath)

        data_service = context.find_service("DataService")
        root_node =  data_service.root_node()

        relPath = root_node.get_prop("url") + '\\' + relPath
        self.show_symbol(relPath, line=linefocus, col=0)

    def show_symbol (self,file_path, line, col = 0):
        global node_editors
        url = file_path
        url = url.lower().replace("/", "\\")
        
        if url not in node_editors:
            try:
                widget_ = ScintillaEditor(self._parent)
                widget_.on_open_url(url)
                widget = widget_
                self._parent.addTab(widget, os.path.basename(url))
            except BaseException as e:
                print(e)
                pass
        else:
            widget = node_editors[url]

        if (widget):
            self._parent.setCurrentWidget(widget)
            widget.setCursorPosition(line, col)
            widget.ensureCursorVisible()
            widget.setFocus()
        
    def dragEnterEvent (self,event):
        super().dragEnterEvent(event)
        print("dragEnterEvent")
    
    def dragLeaveEvent (self,event):
        super().dragLeaveEvent(event)
        print("dragLeaveEvent")

    def dragMoveEvent (self,event):
        super().dragMoveEvent(event)
        print("dragMoveEvent")
    
    def dropEvent (self,event):
        global node_editors
        super().dropEvent(event)
        print("dropEvent")
        urls = event.mimeData().urls()
        text = event.mimeData().text()

        print(urls)
        print(text)

        widget = None
        for url in urls:
            url = url.toString(QUrl.PreferLocalFile)
            print(url)
            if url.lower().replace("/", "\\") not in node_editors:
                try:
                    widget_ = ScintillaEditor(self._parent)
                    widget_.on_open_url(url)
                    widget = widget_
                    self._parent.addTab(widget, os.path.basename(url))
                except BaseException as e:
                    print(e)
                    pass
        if (widget):
            self._parent.setCurrentWidget(widget)

    def on_open_url(self, url):
        self._init = True
        global ex_config

        file_path = url
        file_ex = file_path.split(".")[-1].lower()
        self.setText(open(url, "r", encoding="UTF-8").read())
        #print("====================>>>>>>>>>>>>>==========")
        #print(self.text())
        #print(bytes(self.text(), "utf-8"))

        self._url = url
        global node_editors
        node_editors[url.lower().replace('/', '\\')] = self

        self._init = False
        return ""
    def on_init(self, node):
        
        self._node = node
        return self.on_open_url(node.get_prop("url"))

    def onTextChanged(self):
        #super().onTextChanged()
        if self._init == True:
            return 

        index = self._parent.indexOf(self)
        self._parent.setTabIcon(index, QIcon("C:\\Work\\QtUi\\AIDE\\plugins\\CodeEditor\\resource\\modify.png"))

    def on_find(self):
        self._find = {}
        dialog = QDialog(self)
        vlayout = QVBoxLayout()

        input = QLineEdit(dialog)
        is_case = QCheckBox("大小写", dialog)
        is_word = QCheckBox("全字", dialog)
        button_ok = QPushButton("查找", dialog)
        button_ok.clicked.connect(dialog.accept)

        vlayout.addWidget(input)
        vlayout.addWidget(is_case)
        vlayout.addWidget(is_word)
        vlayout.addWidget(button_ok)
        dialog.setLayout(vlayout)

        if QDialog.Accepted != dialog.exec_():
            return 

        self.findFirst(input.text(), False, is_case.checkState()==2, is_word.checkState()==2, True)
        
    def on_find_next(self):
        self.findNext()
        pass
    
    def on_find_pre(self):
        pass
        
    def on_save(self):
        print("on save....")
        if self._node:
            file_path = self._node.get_prop("url")
        else:
            file_path = self._url

        try:
            #print("====================>>>>>>>>>>>>>")
            #print(self.text())
            #print(bytes(self.text(), "utf-8"))
            open(file_path, "w", encoding="UTF-8").write(self.text())
            index = self._parent.indexOf(self)
            self._parent.setTabIcon(index, QIcon(""))
        except BaseException as e:
            print(e)

        return ""
    def on_close(self):
        global node_editors
        del node_editors[self._url.lower().replace('/', '\\')]
        return ""
    ''''''

'''=== end class ==='''

class SimplePythonEditor(QsciScintilla):
    ARROW_MARKER_NUM = 8

    def __init__(self, parent=None):
        super(SimplePythonEditor, self).__init__(parent)
        self._parent = parent
        self._url = None
        self._init = False
        
        self._find = {}

        self.selAllShort = QShortcut(QKeySequence("Ctrl+s"), self)
        self.selAllShort.activated.connect(self.on_save)
        
        self.findShort = QShortcut(QKeySequence("Ctrl+f"), self)
        self.findShort.activated.connect(self.on_find)
        
        self.findNextShort = QShortcut(QKeySequence("F3"), self)
        self.findNextShort.activated.connect(self.on_find_next)
        
        self.findPreShort = QShortcut(QKeySequence("Shift+F3"), self)
        self.findPreShort.activated.connect(self.on_find_pre)

        self.goDefine = QShortcut(QKeySequence("F12"), self)
        self.goDefine.activated.connect(self.on_go_define)

        self.goback = QShortcut(QKeySequence("Alt+Left"), self)
        self.goback.activated.connect(self.on_go_back)

        self.goforward = QShortcut(QKeySequence("Alt+Right"), self)
        self.goforward.activated.connect(self.on_go_forward)

        self.textChanged.connect(self.onTextChanged)
        self.cursorPositionChanged.connect(self.on_cursor_change)

        self.setAutoIndent(True)
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)

        self.setEolMode(QsciScintilla.EolUnix)

        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
        self.setAutoCompletionSource(self.AcsAll)  # 自动补全。对于所有Ascii字符
        self.setAutoCompletionCaseSensitivity(False)  # 自动补全大小写敏感
        self.setAutoCompletionThreshold(1)  # 输入多少个字符才弹出补全提示

        

        # Set the default font
        font = QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self._font = font
        self.setFont(font)
        self.setMarginsFont(font)

        self.setUtf8(True)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#cccccc"))

        # Clickable margin 1 for showing markers
        self.setMarginSensitivity(1, True)
        self.marginClicked.connect(self.on_margin_clicked)
        #self.connect(self,
        #    SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
        #    self.on_margin_clicked)
        self.markerDefine(QsciScintilla.RightArrow,
            self.ARROW_MARKER_NUM)
        self.setMarkerBackgroundColor(QColor("#ee1111"),
            self.ARROW_MARKER_NUM)

        # Brace matching: enable for a brace immediately before or after
        # the current position
        #
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#ffe4e4"))

        # Set Python lexer
        # Set style for Python comments (style number 1) to a fixed-width
        # courier.
        #
        
        #print(QsciScintilla.SCI_STYLESETFONT)
        #self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')
        #self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1)

        # Don't want to see the horizontal scrollbar at all
        # Use raw message to Scintilla here (all messages are documented
        # here: http://www.scintilla.org/ScintillaDoc.html)
        #self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        # not too small
        self.setMinimumSize(600, 450)

    def on_margin_clicked(self, nmargin, nline, modifiers):
        # Toggle marker for the line the margin was clicked on
        if self.markersAtLine(nline) != 0:
            self.markerDelete(nline, self.ARROW_MARKER_NUM)
        else:
            self.markerAdd(nline, self.ARROW_MARKER_NUM)
    
    def indicator_clicked(self, line, index, keys):
        #self.indicator_clicked_delayed( line, index, keys)
        QTimer.singleShot(100, functools.partial(
            self.indicator_clicked_delayed, line, index, keys))
        
    ''''''

    def on_go_back(self):
        cursor_s = goback_cursor()
        if cursor_s == None:
            return 

        self.show_symbol(cursor_s['url'], cursor_s['line'], cursor_s['index'])

    def on_go_forward(self):
        cursor_s = goforward_cursor()
        if cursor_s == None:
            return 
            
        self.show_symbol(cursor_s['url'], cursor_s['line'], cursor_s['index'])

    def on_cursor_change(self, line, index):
        cursor_change(self._url, line, index)

    def on_go_define(self):
        line, col = self.getCursorPosition()
        self.indicator_clicked_delayed(line, col, None)

    def indicator_clicked_delayed(self, line, index, keys):
        global context

        pos = self.positionFromLineIndex(line, index)
        print("pos:", line, index, pos)

        start = self.SendScintilla(QsciScintilla.SCI_WORDSTARTPOSITION,  pos) # 纯英文下，运行正常
        end = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION,  pos)
        #start = self.SendScintilla(QsciScintilla.SCI_INDICATORSTART, 0, pos)
        #end = self.SendScintilla(QsciScintilla.SCI_INDICATOREND, 0, pos)
        #text = self.text()[start:end]
        text = self.text(start, end)
        #print('text:', text, text2)
        print("pos:", line, index, pos, start, end)
        print("indicator '{}' clicked in line '{}', index '{}'".format(text, line, index))

        relPath, line = Ctags_parser.Instance().where_to_jump(text)
        linefocus = int(line) - 1
        print("jump to file: " + relPath)

        data_service = context.find_service("DataService")
        root_node =  data_service.root_node()

        relPath = root_node.get_prop("url") + '\\' + relPath
        self.show_symbol(relPath, line=linefocus, col=0)

    def show_symbol (self,file_path, line, col = 0):
        global node_editors
        url = file_path
        url = url.lower().replace("/", "\\")
        
        if url not in node_editors:
            try:
                widget_ = SimplePythonEditor(self._parent)
                widget_.on_open_url(url)
                widget = widget_
                self._parent.addTab(widget, os.path.basename(url))
            except BaseException as e:
                print(e)
                pass
        else:
            widget = node_editors[url]

        if (widget):
            self._parent.setCurrentWidget(widget)
            widget.setCursorPosition(line, col)
            widget.ensureCursorVisible()
            widget.setFocus()

    def dragEnterEvent (self,event):
        super().dragEnterEvent(event)
        print("dragEnterEvent")
    
    def dragLeaveEvent (self,event):
        super().dragLeaveEvent(event)
        print("dragLeaveEvent")

    def dragMoveEvent (self,event):
        super().dragMoveEvent(event)
        print("dragMoveEvent")
    
    def dropEvent (self,event):
        global node_editors
        super().dropEvent(event)
        print("dropEvent")
        urls = event.mimeData().urls()
        text = event.mimeData().text()

        print(urls)
        print(text)

        widget = None
        for url in urls:
            url = url.toString(QUrl.PreferLocalFile)
            print(url)
            if url.lower().replace("/", "\\") not in node_editors:
                try:
                    widget_ = SimplePythonEditor(self._parent)
                    widget_.on_open_url(url)
                    widget = widget_
                    self._parent.addTab(widget, os.path.basename(url))
                except BaseException as e:
                    print(e)
                    pass
        if (widget):
            self._parent.setCurrentWidget(widget)
    
    def on_open_url(self, url):
        self._init = True
        global ex_config

        file_path = url
        file_ex = file_path.split(".")[-1].lower()
        if file_ex in ex_config:
            ex_config[file_ex].setDefaultFont(self._font)
            self.setLexer(ex_config[file_ex])
        self.setText(open(url, "r", encoding="UTF-8").read())
        #print("====================>>>>>>>>>>>>>==========")
        #print(self.text())
        #print(bytes(self.text(), "utf-8"))

        self._url = url
        global node_editors
        node_editors[url.lower().replace('/', '\\')] = self

        self._init = False
        return ""
    def on_init(self, node):
        
        self._node = node
        return self.on_open_url(node.get_prop("url"))

    def onTextChanged(self):
        #super().onTextChanged()
        if self._init == True:
            return 

        index = self._parent.indexOf(self)
        self._parent.setTabIcon(index, QIcon("C:\\Work\\QtUi\\AIDE\\plugins\\CodeEditor\\resource\\modify.png"))

    def on_find(self):
        self._find = {}
        dialog = QDialog(self)
        vlayout = QVBoxLayout()

        input = QLineEdit(dialog)
        is_case = QCheckBox("大小写", dialog)
        is_word = QCheckBox("全字", dialog)
        button_ok = QPushButton("查找", dialog)
        button_ok.clicked.connect(dialog.accept)

        vlayout.addWidget(input)
        vlayout.addWidget(is_case)
        vlayout.addWidget(is_word)
        vlayout.addWidget(button_ok)
        dialog.setLayout(vlayout)

        if QDialog.Accepted != dialog.exec_():
            return 

        self.findFirst(input.text(), False, is_case.checkState()==2, is_word.checkState()==2, True)
        
    def on_find_next(self):
        self.findNext()
        pass
    
    def on_find_pre(self):
        pass
        
    def on_save(self):
        print("on save....")
        if self._node:
            file_path = self._node.get_prop("url")
        else:
            file_path = self._url

        try:
            #print("====================>>>>>>>>>>>>>")
            #print(self.text())
            #print(bytes(self.text(), "utf-8"))
            open(file_path, "w", encoding="UTF-8").write(self.text())
            index = self._parent.indexOf(self)
            self._parent.setTabIcon(index, QIcon(""))
        except BaseException as e:
            print(e)

        return ""
    def on_close(self):
        global node_editors
        del node_editors[self._url.lower().replace('/', '\\')]
        return ""

class EditorExtension:
    def __init__(self):
        self._node = None
    def data_kind(self):
        return ['Project']
    def is_match_data(self, node):
        global ex_config

        self._node = node
        if node.get_kind() == 'File' and node.get_name().split('.')[-1].lower() in ex_config:
            return True
        else:
            return False
    def create_editor(self, parent):
        global node_editors
        if self._node.get_prop("url").lower().replace('/', '\\') in node_editors:
            return node_editors[self._node.get_prop("url").lower().replace('/', '\\')]
        return SimplePythonEditor(parent)


def onDataNodeDelete(event, node):
    # 删除文件
    # 删除失败，不做处理
    url = node.get_prop("url")
    if os.path.exists(url):
        if os.path.isdir(url):
            os.rmdir(url)
        else:
            os.remove(url)
    # 关闭Editor
    

def update_node_url(node, old_find, new_replace):
    url = node.get_prop("url")
    url_new = url.replace(old_find, new_replace)
    node.set_prop("url", url_new)
    
    children = node.get_children()
    for child in children:
        update_node_url(child, old_find, new_replace)

def onDataNodeModifyName(event, node, str_old_name):
    # 修改文件、文件夹名称
    # 文件夹名称修改，影响所有子节点属性修改
    url = node.get_prop("url")
    old_name = os.path.basename(url)
    url_new = url
    url_new = url_new.replace(old_name, node.get_name())
    print(url)

    print(url_new)
    if os.path.exists(url):
        os.rename(url, url_new)
        
    # 进行url属性修改
    update_node_url(node, url, url_new)

def onDataNodeModifyProp(event, node, prop_key, old_prop_value):
    pass

def OnOpenProject(event, node):
    Ctags_parser.Instance().build_ctags(node.get_prop("url"))

config = {
    'pluginid': 'UI::Core::SimpleEditor',
    "extensions" : [
        {
            "name": "PL::Basic::Editor",
            "id": "PL::Basic::Editor::CodeEditor",
            "impl": EditorExtension()
        },
    ],

    "subscribes" : [
        {
            "name": 'event::Project::Open',
            "define": OnOpenProject
        },
        {
            'name': 'event::DataNode::Delete',
            "define": onDataNodeDelete
        },
        {
            'name': 'event::DataNode::Modify::Name',
            "define": onDataNodeModifyName
        },
        {
            'name': 'event::DataNode::Modify::Prop',
            "define": onDataNodeModifyProp
        }
    ],
}